# 孤立变点聚类，初步估计异常区间
# 变点检测在另一个文件里
# 所有孤立变点的聚类，初步判断故障时间间隔
import pandas as pd
import numpy as np
from scipy.spatial.distance import cdist
import random
from bayesian_changepoint_detection.bayesian_models import online_changepoint_detection
from bayesian_changepoint_detection.online_likelihoods import StudentT
from bayesian_changepoint_detection.hazard_functions import constant_hazard
from functools import partial
import bayesian_changepoint_detection.online_likelihoods as online_ll


hazard_function = partial(constant_hazard, 250)
def find_interval(data):
    R, maxes = online_changepoint_detection(
        data, hazard_function, online_ll.StudentT(alpha=0.1, beta=.01, kappa=1, mu=0)
    )
    Nw = 10
    changepoints = [i for i, value in enumerate(maxes) if value == 0.0 or value == 1.0]
    return changepoints


def calculate_distance(interval_vector):
    A = np.array(interval_vector)
    B = np.array(interval_vector)
    dist = cdist(A, B, metric='euclidean')
    return dist


# DBSCAN算法，参数为数据集，Eps为指定半径参数，MinPts为制定邻域密度阈值
def dbscan(Data, metric_list, Eps, MinPts):
    num = len(metric_list)  # 点的个数
    # print("点的个数："+str(num))
    unvisited = [i for i in range(num)]  # 没有访问到的点的列表
    # print(unvisited)
    visited = []  # 已经访问的点的列表
    C = [-1 for i in range(num)]
    # C为输出结果，默认是一个长度为num的值全为-1的列表
    # 用k来标记不同的簇，k = -1表示噪声点
    k = -1

    # 如果还有没访问的点
    while len(unvisited) > 0:
        # 随机选择一个unvisited对象
        p = random.choice(unvisited)
        unvisited.remove(p)
        visited.append(p)
        # N为p的epsilon邻域中的对象的集合
        N = []
        for i in range(num):
            if Data.loc[metric_list[i], metric_list[p]] <= Eps:  # and (i!=p):
                N.append(i)
        # 如果p的epsilon邻域中的对象数大于指定阈值，说明p是一个核心对象
        if len(N) >= MinPts:
            k = k + 1
            # print(k)
            C[p] = k
            # 对于p的epsilon邻域中的每个对象pi
            for pi in N:
                if pi in unvisited:
                    unvisited.remove(pi)
                    visited.append(pi)
                    # 找到pi的邻域中的核心对象，将这些对象放入N中
                    # M是位于pi的邻域中的点的列表
                    M = []
                    for j in range(num):
                        if Data.loc[metric_list[j], metric_list[pi]] <= Eps:  # and (j!=pi):
                            M.append(j)
                    if len(M) >= MinPts:
                        for t in M:
                            if t not in N:
                                N.append(t)
                # 若pi不属于任何簇，C[pi] == -1说明C中第pi个值没有改动
                if C[pi] == -1:
                    C[pi] = k
        # 如果p的epsilon邻域中的对象数小于指定阈值，说明p是一个噪声点
        else:
            C[p] = -1
    return C
def dict_to_dataframe(data_dict):
    max_length = max(len(lst) for lst in data_dict.values())

    # 创建一个空的DataFrame，列名为字典的键，行数为最大长度
    df = pd.DataFrame(index=range(max_length), columns=data_dict.keys())

    # 填充DataFrame，较短的列表用NaN填充
    for key, lst in data_dict.items():
        df[key] = pd.Series(lst).reindex(range(max_length), fill_value=np.nan)

    return df

if __name__ == '__main__':
    # metric_name = pd.read_csv('D:/时间序列/Benchmark/指标挑选/bursty_input-slide_dtw/2_1_100000_60_selected.csv', index_col=0)
    # metric_name = metric_name[(metric_name['key'] == 1) | (metric_name['key'] == 2)]
    # selected = metric_name['metric'].tolist()
    selected = ['1_executor_cpuTime_count', '1_executor_shuffleLocalBytesRead_count', '1_executor_shuffleRecordsRead_count', '1_executor_shuffleRemoteBytesRead_count', '1_executor_shuffleTotalBytesRead_count', '1_jvm_heap_committed_value', '1_jvm_heap_usage_value', '1_jvm_heap_used_value', '1_jvm_pools_PS-Eden-Space_max_value', '1_jvm_pools_PS-Eden-Space_usage_value', '1_jvm_pools_PS-Eden-Space_used_value', '1_jvm_pools_PS-Old-Gen_used_value', '1_jvm_pools_PS-Survivor-Space_committed_value', '1_jvm_pools_PS-Survivor-Space_max_value', '1_jvm_pools_PS-Survivor-Space_usage_value', '1_jvm_pools_PS-Survivor-Space_used_value', '1_jvm_total_committed_value', '1_jvm_total_used_value', '2_jvm_heap_usage_value', '2_jvm_heap_used_value', '2_jvm_pools_PS-Eden-Space_usage_value', '2_jvm_pools_PS-Eden-Space_used_value', '2_jvm_pools_PS-Survivor-Space_committed_value', '2_jvm_pools_PS-Survivor-Space_max_value', '2_jvm_pools_PS-Survivor-Space_used_value', '2_jvm_total_used_value', 'driver_BlockManager_memory_memUsed_MB_value', 'driver_BlockManager_memory_onHeapMemUsed_MB_value', 'driver_BlockManager_memory_remainingMem_MB_value', 'driver_BlockManager_memory_remainingOnHeapMem_MB_value']

    change_point_df = pd.read_csv('E:\project\TimeList\Reform/step1/bursty_input/2_1_100000_60_dtw_2.csv')
    change_point_df = change_point_df[selected]
    interval_vector = {}
    for column in change_point_df.columns:
        interval_vector[column] = find_interval(change_point_df[column])
    interval_vector = dict_to_dataframe(interval_vector)


    dist = calculate_distance(interval_vector)
    dist = pd.DataFrame(dist)

    cluster = dbscan(dist, dist.columns.tolist(), 2, int(len(selected)*0.7))
    print(cluster)
    print(set(cluster))

    if -1 in set(cluster):
        K = len(set(cluster))-1
    else:
        K = len(set(cluster))

    con = list(zip(cluster, interval_vector))
    for clu in range(K):
        names = globals()
        names['pre'+str(clu)] = [i[1] for i in con if i[0] == clu]
        print(names['pre'+str(clu)])


